﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace LittleBee.JsonRpc
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
	public class JsonRpcMethodAttribute : Attribute
	{ }

	public class JsonRpcMethodDuplicatedException : ApplicationException
	{
		public JsonRpcMethodDuplicatedException(string msg)
		: base(msg)
		{ }
	}

	/// <summary>
	/// jsonrpc interface version
	/// format: major.senor.patch
	/// major: 大的interface函数功能升级
	/// senor: 小的个别inteface函数功能修改
	/// patch：个别函数的修订，不涉及各函数接口功能的修改
	/// 高版本接口兼容低版本接口
	/// </summary>
	[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
	public class JsonRpcVersionAttribute : Attribute
	{
		public JsonRpcVersionAttribute()
		{

		}

		public JsonRpcVersionAttribute(string ver)
		{
			Version = ver;
		}

		public string Version { get; } = "0.0.0";
	}

	[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
	public class JsonRpcMethodExampleAttribute : Attribute
	{
		public JsonRpcMethodExampleAttribute() { }
		public JsonRpcMethodExampleAttribute(string sample) { MethodSample = sample; }

		public string MethodSample { get; } = "{}";
	}


	public class JsonRpcServer<T>
	{
		string _strVersion = "0.0.0";
		TcpListener? _tcpListener;
		Dictionary<string, MethodInfo> _dctJsonRpcMethods = new();
		Dictionary<NetworkStream, Task> _dctClientConnections = new();
		Dictionary<NetworkStream, TcpClient> _dctStream2Client = new();

		private JsonRpcServer()
		{
		}

		public JsonRpcServer(int port, string ipaddr = "127.0.0.1")
		{
			Type type = typeof(T);

			foreach (Attribute attr in type.GetCustomAttributes(true))
			{
				var ver = attr as JsonRpcVersionAttribute;
				if (null != ver)
				{
					_strVersion = ver.Version;
				}
			}

			foreach (MethodInfo m in type.GetMethods())
			{
				if (m.IsPublic && m.GetCustomAttribute(typeof(JsonRpcMethodAttribute)) != null)
				{
					if (_dctJsonRpcMethods.ContainsKey(m.Name))
						throw new JsonRpcMethodDuplicatedException($"Method name {m.Name} is duplicated.");

					_dctJsonRpcMethods[m.Name] = m;
				}
			}

			_dctJsonRpcMethods["GetInterfacesInfo"] = this.GetType().GetMethod("GetInterfacesInfo")!;

			_tcpListener = new TcpListener(IPAddress.Parse(ipaddr), port);
		}

		public string Version
		{
			get { return _strVersion; }
		}

		public bool GetInterfacesInfo(JToken jtParam, out JToken? jtResult)
		{
			JObject joRet = new JObject();

			joRet["version"] = Version;
			//joRet["interfaces"] = JArray.FromObject(_dctJsonRpcMethods.Keys);
			JObject joInterfaces= new JObject();

			foreach (var item in _dctJsonRpcMethods)
			{
				if (item.Key == "GetInterfacesInfo")
					continue;

				JArray jaExamples = new JArray();
				var attrs = item.Value.GetCustomAttributes<JsonRpcMethodExampleAttribute>();
				if (null == attrs)
				{
					jaExamples.Add("{}");
				}
				else
				{
					foreach (var example in attrs)
					{
						jaExamples.Add(example.MethodSample.ToString());
					}
				}

				joInterfaces[item.Key] = jaExamples;
			}

			joRet["interfaces"] = joInterfaces;
			jtResult = joRet;

			return true;
		}

		class AsyncState
		{
			public AsyncState(Stream stream, byte[] buffer, MemoryStream ms)
			{
				Stream = stream;
				Buffer = buffer;
				MemoryStream = ms;
			}
			public Stream Stream { get; set; }
			public byte[] Buffer { get; set; }
			public MemoryStream MemoryStream { get; set; }
			public ManualResetEvent  EvtHandle { get; set; } = new ManualResetEvent(false);
			public Exception? Exception { get; set; }
		}

		public void Start()
		{
			ConcurrentQueue<AsyncState> queueAnsycRequest = new ConcurrentQueue<AsyncState>();

			/// JSONRPC request's processing task:
			Task.Run(() =>
			{
				while (true)
				{
					// try to get one request message and to process it
					if (queueAnsycRequest.TryDequeue(out AsyncState? asyncState))
					{
						JToken? jtSid = null;
						JObject joResp = new JObject
						{
							["jsonrpc"] = "2.0"
						}; //the response object to client

						try
						{
							string? strReq = Encoding.UTF8.GetString(asyncState!.MemoryStream!.ToArray()); // throw ArgumentException
							Console.WriteLine(strReq);

							JObject joReq = JObject.Parse(strReq);  // throw JsonReaderException

							if (joReq.TryGetValue("id", out jtSid))
							{
								joResp["id"] = jtSid;
							}

							// check the jsonrpc message
							if (joReq.TryGetValue("jsonrpc", out JToken? jtJsonRpc)
								&& jtJsonRpc.Type == JTokenType.String
								&& jtJsonRpc.Value<string>() == "2.0"
								&& joReq.TryGetValue("method", out JToken? jtMethod)
								&& jtMethod.Type == JTokenType.String)
							{
								joReq.TryGetValue("params", out JToken? jtParams);

								string strMethod = jtMethod.Value<string>()!;

								if (_dctJsonRpcMethods.TryGetValue(strMethod, out MethodInfo? mi))
								{
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
#pragma warning disable CS8601 // Possible null reference assignment.
									Object[] mParameters = new object[2] { jtParams, null };
#pragma warning restore CS8601 // Possible null reference assignment.
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.

									bool ret = (bool)mi!.Invoke(this, mParameters)!;

									if (jtSid != null)
									{
										if (ret == true)
										{
											joResp["result"] = mParameters[1] as JToken;
										}
										else
										{
											joResp["error"] = mParameters[1] as JToken;
										}
									}
								}
								else
								{
									joResp["code"] = -32601;
									joResp["message"] = $"Method not found: {jtMethod}";
								}
							}
							else
							{
								joResp["code"] = -32600;
								joResp["message"] = "Invalid JsonRpc Request";
							}
						}
						catch (ArgumentException)
						{
							joResp["code"] = -32600;
							joResp["message"] = "Invalid Parameters Request";
						}
						catch (JsonReaderException)
						{
							joResp["code"] = -32700;
							joResp["message"] = "Json Parse Failed";
						}
						catch (Exception ex)
						{
							joResp["code"] = -32700;
							joResp["message"] = $"Unknown Json Error: {ex.Message}";
						}

						if (jtSid != null)
						{
							string strResp = joResp.ToString(Newtonsoft.Json.Formatting.None);
							Console.WriteLine(strResp);
							byte[] abResp = Encoding.UTF8.GetBytes(strResp);
							asyncState!.Stream.Write(abResp, 0, abResp.Length);
						}
					}
                    else
                    {
						Thread.Sleep(20);
                    }
				}
			});

			/// jsonrpc server task: 
			Task.Run(() =>
			{
				try
				{
					_tcpListener!.Start();
					while (true)
					{
						TcpClient tcpClient;
						NetworkStream stream;

						try
						{
							tcpClient = _tcpListener.AcceptTcpClient();
							stream = tcpClient.GetStream();
						}
						catch
						{
							break;
						}

						_dctStream2Client[stream] = tcpClient;
						Console.WriteLine($"client: {tcpClient.Client.LocalEndPoint} connected");

						_dctClientConnections[stream] = Task.Run(() =>
						{
							try
							{
								byte[] buffer = new byte[64 * 1024];
								while (true)
								{
									try
									{
										MemoryStream ms = new MemoryStream();

										var asyncState = new AsyncState(stream, buffer, ms);

										IAsyncResult asyncResult = stream.BeginRead(buffer, 0, buffer.Length,
											new AsyncCallback(JsonRpcAsyncReadCallback), asyncState);

										bool blAsyncState = asyncState.EvtHandle.WaitOne();

										if (blAsyncState == true)
										{
											if (asyncState.Exception == null && asyncState.MemoryStream.Length > 0)
											{
												queueAnsycRequest.Enqueue(asyncState);
											}
											else
											{
												stream.Close();
												break;
											}
										}
									}
									catch (IOException)
									{
										stream.Close();
										break;
									}
									catch (ObjectDisposedException)
									{
										stream.Close();
										break;
									}
									catch (Exception ex)
									{
										Console.WriteLine(ex.Message);
									}
								}
								_dctClientConnections.Remove(stream);
							}
							catch (Exception ex)
							{
								Console.WriteLine(ex.Message);
							}
						});
					}
				}
				catch (SocketException ex)
				{
					Console.WriteLine(ex.Message);
				}
				catch (ObjectDisposedException)
				{
					// to-do:
					_tcpListener!.Stop();
					_tcpListener = null;
				}
			});
		}

		public void Stop()
		{
			foreach (var item in _dctClientConnections)
			{
				item.Key.Close();
				if (item.Value.Wait(1000, new CancellationToken(true)) == false)
				{
					item.Value.Wait();
				}
			}

			_tcpListener!.Stop();
		}

		private void JsonRpcAsyncReadCallback(IAsyncResult ar)
		{
			var asyncState = (AsyncState)ar.AsyncState!;
			try
			{
				int nBytesRead = asyncState.Stream.EndRead(ar);

				if (nBytesRead == asyncState.Buffer!.Length)
				{
					asyncState.MemoryStream.Write(asyncState.Buffer, 0, nBytesRead);
					asyncState.Stream.BeginRead(asyncState.Buffer, 0, nBytesRead, new AsyncCallback(JsonRpcAsyncReadCallback), asyncState);
				}
				else
				{
					asyncState.MemoryStream.Write(asyncState.Buffer, 0, nBytesRead);
					asyncState.EvtHandle.Set();
				}
			}
			catch (IOException ex)
			{
				Console.WriteLine($"client: {_dctStream2Client[(NetworkStream)asyncState.Stream].Client.LocalEndPoint} closed");
				Console.WriteLine($"{ex.Message}");
				asyncState.Exception = ex;
				asyncState.EvtHandle.Set();
			}
			catch (ObjectDisposedException ex)
			{
				Console.WriteLine($"client: {asyncState.Stream} closed");
				Console.WriteLine($"{ex.Message}");
				asyncState.Exception = ex;
				asyncState.EvtHandle.Set();
			}
			catch (Exception ex)
			{
				Console.WriteLine($"Exception occurred: {ex.Message}");
			}
		}
	}
}
